feat(provider): add aimlapi.com as a first-class LLM provider - #1
Open
Lookoff-AIMLAPI wants to merge 3 commits into
Open
feat(provider): add aimlapi.com as a first-class LLM provider#1Lookoff-AIMLAPI wants to merge 3 commits into
Lookoff-AIMLAPI wants to merge 3 commits into
Conversation
PentAGI already advertises an aggregator tier next to its per-vendor providers, but reaching aimlapi.com meant configuring the generic `custom` provider by hand: one endpoint, one key, no model catalog, no per-agent defaults and no entry in the settings UI. Since the gateway is OpenAI-compatible it fits the existing openaicompat base exactly, so making it first-class costs a registry entry and a config rather than a new client. The default roster spreads the 13 agent roles over four model families and deliberately skips the OpenAI/Anthropic/Google models the gateway also carries: this product runs offensive-security workloads and those vendors' guardrails false-positive on legitimate exploit-development content. That is the same reasoning already recorded in examples/configs/openrouter.provider.yml, and the role/model mapping mirrors it so a reviewer can compare them directly. The enricher is the one place the mapping diverges. Through this gateway minimax/minimax-m3 ignores both reasoning_effort:"none" and extra_body.thinking.type:"disabled" and still thinks, returning the chain inline in `content` wrapped in <think> tags instead of in reasoning_content; the enricher is the role that explicitly wants thinking off, so it stays on deepseek-v4-flash, which honours it. Requests to api.aimlapi.com carry HTTP-Referer, X-Title, X-AIMLAPI-Source and X-AIMLAPI-Partner-ID identifying PentAGI as the calling application. They are attached by a RoundTripper that wraps a copy of the shared HTTP client, so the process-wide client is never mutated, a caller's own header always wins, and the host is re-checked per request — pointing AIMLAPI_SERVER_URL at a LiteLLM proxy or a self-hosted gateway disables attribution rather than tagging another operator's traffic through a redirect. Two regression tests exist because the failures they catch are invisible otherwise: a malformed partner id is accepted by the gateway and silently earns nothing, and the gateway rejects `null` for temperature, top_p, seed, tools and most other optional fields with a 400 while accepting them absent — so a client that serialises cleared tools as null passes turn one of an agent loop and fails every turn two.
Moves aimlapi.com to the front of the hand-ordered provider lists: the registry, the provider-type whitelist, the installer wizard's provider menu, the .env.example block, and the README/config.md provider sections and their tables of contents. This is placement, not function — it is separated so it can be dropped before the provider itself is offered upstream, where ordering is the maintainer's call and not a contributor's. Two lists are deliberately left alone. The settings-page label map and the provider-icon map are keyed alphabetically, so aimlapi.com already sorts first there without touching the ordering; and the GraphQL ProviderType enum is append-only by convention, with reordering it forcing a codegen churn that buys nothing a user can see.
The placeholder part_pentagi was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_6bffrRIYBS8OtYbQhsEPi0SS. A wrong or unknown partner id is accepted with a 200 and silently not attributed, so this would not have surfaced at runtime.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of the Change
Problem
PentAGI's README already advertises an aggregator tier next to its per-vendor providers, but reaching aimlapi.com meant hand-configuring the generic
customprovider: one endpoint, one key, no model catalog, no per-agent defaults, no entry in the Settings UI, and noctestervalidation report.grep -rIin "aimlapi"over the tree returned 0 matches before this PR.Solution
Adds
aimlapias a first-classProviderType, following the eight steps inCLAUDE.md→ "Adding a New LLM Provider" and the shape of the last providers added (minimax,qwen,kimi). The gateway is OpenAI-compatible, so the provider is ~85 lines on top of the existingopenaicompatbase rather than a new client:backend/pkg/providers/aimlapi/— provider, embeddedconfig.yml(13 agent roles) andmodels.yml(6 models), attribution transport, testsprovider.ProviderAIMLAPI+DefaultProviderNameAIMLAPI, registry entry,AllProviderTypes(this is what the REST layer'sValid()whitelist reads, so no separate change there)AIMLAPI_API_KEY/AIMLAPI_SERVER_URL/AIMLAPI_PROVIDERinconfig.go,.env.example,docker-compose.ymlaimlapito thePROVIDER_TYPEenum, plusdatabase.ProviderTypeAimlapigenerated.go/models_gen.go/types.ts, resolver casesaimlapi.com), installer wizard screen,ctester/ftesterprovider optionsbackend/docs/config.md,backend/docs/database.md,.vscode/launch.jsonexamples/tests/aimlapi-report.md— thectester -agents allvalidation run every shipped provider config carriesModel roster. The 13 roles are spread over four model families, deliberately excluding the OpenAI/Anthropic/Google models the gateway also carries — this product runs offensive-security workloads and those vendors' guardrails false-positive on legitimate exploit-development content. That is the same reasoning recorded in
examples/configs/openrouter.provider.yml, and the role→model mapping mirrors it so the two can be compared side by side.deepseek/deepseek-v4-flashz-ai/glm-5-turbozhipu/glm-5.2minimax/minimax-m3moonshot/kimi-k2-7-codedeepseek/deepseek-v4-proalso ships inmodels.ymlfor selection in the UI. Every id was verified against both theidset and thealiasesset ofGET https://api.aimlapi.com/v1/models?include=allon 2026-09-03, and each is a canonical id, not an alias — an alias on this gateway can resolve to a different model than its name implies (deepseek/deepseek-chatis published as its own model and as an alias ofdeepseek-v4-flash). No model was removed from anything, so no deletion-on-catalog-absence judgement was made. ATestModelsAreCatalogIdsunit test pins the config to the shipped catalog.The enricher is the one place the mapping diverges from the OpenRouter config. Through this gateway
minimax/minimax-m3ignores bothreasoning_effort:"none"andextra_body.thinking.type:"disabled"and still thinks, returning the chain inline incontentwrapped in<think>tags rather than inreasoning_content. The enricher is the role that explicitly wants thinking off, so it stays ondeepseek-v4-flash, which honours it. This is documented inconfig.yml; the same config passes thereasoning_offcapability test on OpenRouter, so the difference is gateway-side.Attribution. Requests to
api.aimlapi.comcarryHTTP-RefererandX-Title(pointing at PentAGI, per the OpenRouter convention — they identify the calling application, not the gateway) plusX-AIMLAPI-SourceandX-AIMLAPI-Partner-ID. Implementation notes, all covered by tests:RoundTripperwrapping a copy of the sharedsystem.GetHTTPClientresult, so the process-wide client is never mutatedRoundTriptags a clone rather than the request it was handedaimlapi.comand its subdomains: pointingAIMLAPI_SERVER_URLat a LiteLLM proxy or a self-hosted gateway that fronts the API disables the headers instead of tagging another operator's trafficThe final commit,
chore(aimlapi): fork-only placement — do not send upstream, contains nothing but ordering (registry, whitelist, wizard menu,.env.example, README/docs section order). It is separated so it can be dropped before this is offered upstream, where list ordering is the maintainer's call.Type of Change
Areas Affected
Testing and Verification
Test Configuration
Test Steps
cd backend && go build ./...andgo test ./...on a pristine checkout of the fork's default branch, to record a baseline.cd frontend && pnpm install && pnpm typescript && pnpm lint && pnpm prettier && pnpm test, same baseline.go run github.com/99designs/gqlgen --config gqlgen/gqlgen.ymlandpnpm run graphql:generate— the committed generated files are codegen output, not hand edits.ctester -type aimlapi -agents all -groups all, which builds the provider through the registry and calls the live API for all 13 roles.aimlapi.New(...).CallWithTools(...), with tools deliberately cleared on turn two.Test Results
Backend —
go build ./...exit 0.cmd/installer,cmd/installer/files,pkg/server/authcmd/installer,cmd/installer/filesThe two remaining failures are pre-existing and unrelated (
cannot read .env file: embedded provider not initialized— the installer's embedded assets are not generated in a plain source checkout).pkg/server/auth'sTestUserCache_ConcurrentAccessfailed on the baseline run and passed afterwards; it is a timing-sensitive concurrency test, not something this change touches.+2 ok= the newpkg/providers/aimlapipackage plus that flake going green.go vetclean on every package touched.Frontend —
pnpm typescript,pnpm lintandpnpm prettierall exit 0.No test file fails after the change that did not fail before it. All of these failures are the same
localStorage/Cannot read properties of undefined (reading 'getItem')environment issue in this sandbox, identical line-for-line to the baseline; three of them happened to pass on the second run.Live provider validation —
ctester -type aimlapi -agents all -groups all -workers 4, full output committed asexamples/tests/aimlapi-report.md:For calibration against what is already in
examples/tests/:openrouter-report.md289/299 (96.66%),deepinfra-report.md288/295 (97.63%),minimax-report.md275/295 (93.22%). All fourreasoning_offcapability tests pass. The seven failures are model-behaviour variance (a model choosing a different tool, or a unified diff that applies but lands the line in the wrong place), except one504from the gateway after 90s on a trivial "count from 1 to 5" prompt — retried fine.Live two-turn tool loop, through
aimlapi.New(...)→CallWithTools, tools present on turn 1 and cleared on turn 2:The two-turn shape matters: this gateway returns 400 for
"tools": null(and for nulltemperature,top_p,seed,tool_choice,response_format,stream,stream_options,max_tokens,max_completion_tokens), while accepting the same fields absent. A client that serialises a cleared field as an explicit null therefore succeeds on turn one of every agent loop and fails on turn two, with a mocked test suite staying green throughout. The Go path is safe — langchaingo declares all of these as pointers/slices withomitempty— andTestRequestOmitsUnsetSamplingParamsandTestToolsAreOmittedNotNulledOnFollowUpTurnassert it against the real serialised bytes so it stays that way.Not verified: the Settings UI and installer wizard changes were checked by type-checker, unit tests and the exhaustive-
Recordcompile guard, not by clicking through a running stack — bringing up Postgres + the full compose stack was out of scope here. The goose migration was reviewed against20260621_120000_add_minimax_provider.sqlbut not applied to a live database.Security Considerations
No new dependencies, so
CONTRIBUTING.md's license gate is unaffected. The API key is read from the environment like every other provider key and is added toGetSecretPatterns(), so it is redacted in logs the same way (theexpectedCountassertion inconfig_test.gomoved 30 → 31 accordingly). The attribution headers carry no user data — a fixed project name, repo URL, source slug and partner id — and are scoped toaimlapi.com, so a misconfiguredAIMLAPI_SERVER_URLcannot leak them to a third party.One upstream-facing note:
GET https://api.aimlapi.com/v1/modelsreturns 200 for any key, including a bogus one or none at all. Nothing in this PR validates a key that way, and nothing should.Performance Impact
One extra
RoundTripperin the chain for this provider only; it compares a hostname and, on a match, clones the request header. No effect on any other provider. Per-role latencies are in the report above.Documentation Updates
CLAUDE.md,backend/docs/config.md,backend/docs/database.md,.env.example,docker-compose.yml,.vscode/launch.json,examples/tests/aimlapi-report.mdDeployment Notes
New optional environment variables:
AIMLAPI_API_KEY,AIMLAPI_SERVER_URL(defaulthttps://api.aimlapi.com/v1),AIMLAPI_PROVIDER. The provider is inert unlessAIMLAPI_API_KEYis set. The goose migration20260903_120000_add_aimlapi_provider.sqlrebuilds thePROVIDER_TYPEenum to addaimlapi; itsDowndeletes rows using the new type before reverting, exactly as the MiniMax migration does.Checklist
Code Quality
go fmtandgo vet(for Go code)pnpm run lint(for TypeScript/JavaScript code)Security
Compatibility
Documentation
Additional Notes
AIMLAPIToolCallIDTemplateis deliberately empty. The gateway multiplexes many vendors, so the tool-call id format follows the model actually serving the request, not the gateway; leaving it empty lets the shared detector derive it per model instead of asserting one format for the whole provider. (In practicedeepseek/deepseek-v4-flashreturnedcall_00_zJQk8yixO2FpvIqsYEhA2155, which matches the DeepSeek template — but that is a property of the upstream model, not ofaimlapi.)The provider icon in
frontend/src/components/icons/aimlapi.tsxis a plain geometric "hub routing to several vendors" mark drawn incurrentColorlike the others, not a reproduction of any brand asset; drop in the real SVG when there is one.No
examples/configs/aimlapi.provider.ymlis included: that file pattern exists for backends reached through the genericcustomprovider, and this one now ships its own embeddedconfig.yml.